package no.gnet.edvd.web.controller;

import static no.gnet.edvd.web.util.Constants.ALL_EVENT_DESCRIPTORS;
import static no.gnet.edvd.web.util.Constants.EVENTODDSMAP_;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import no.gnet.edvd.DataStore;
import no.gnet.edvd.engines.Engine;
import no.gnet.edvd.engines.EventOdds;
import no.gnet.edvd.exception.NoSuchBettableEventException;
import no.gnet.edvd.types.ActivityType;
import no.gnet.edvd.types.Event;
import no.gnet.edvd.types.Option;
import no.gnet.edvd.types.Player;
import no.gnet.edvd.types.Transfer;
import no.gnet.edvd.web.cache.MemCache;
import no.gnet.edvd.web.types.EdvdSessionUser;
import no.gnet.edvd.web.types.exception.EddeVeddeRuntimeException;
import no.gnet.edvd.web.types.exception.IncompleteNewEventException;
import no.gnet.edvd.web.types.exception.NegativeAmountException;
import no.gnet.edvd.web.types.exception.NoSessionUserException;
import no.gnet.edvd.web.types.exception.NoValidAccountException;
import no.gnet.edvd.web.types.exception.NotEnoughCreditException;
import no.gnet.edvd.web.types.exception.NotOwnerOfEventException;
import no.gnet.edvd.web.types.exception.ToohighAmountException;
import no.gnet.edvd.web.types.exception.WrongPasswordException;
import no.gnet.edvd.web.util.Constants;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.ExceptionHandler;

public class CommonController {

	protected static final String SINGLE_EVENT_CACHE_PREFIX = "event_";
	protected static final String ERRORMESSAGE = "errormessage";
	protected static final String SESSIONDIRTY = "SESSIONDIRTY";
	static Logger logger = Logger.getLogger(CommonController.class);

	protected EdvdSessionUser sessionUser(HttpServletRequest request) {
		return (EdvdSessionUser) request.getSession().getAttribute(Constants.EDVDUSER);
	}

	protected Player setSessionUser(HttpServletRequest request, String email, DataStore store) {
		Player player = store.getPlayer(new Player(email));
		if (!store.isActivated(player.id)) {
			throw new EddeVeddeRuntimeException("Player logging on is not activated");
		}
		List<Transfer> unpaidTransfers = store.getUnsettledTransfersForUser(player.id);
		request.getSession().setAttribute(Constants.EDVDUSER,
				EdvdSessionUser.createSessionUser(null, player, true, unpaidTransfers));
		return player;
	}

	protected List<OptionWithOdds> getOptionsWithOddsForEvent(Event event, DataStore datastore, Engine engine) {
		EventOdds odds = (EventOdds) MemCache.get(EVENTODDSMAP_ + event.id);
		if (odds == null) {
			odds = engine.getOddsForEvent(event, datastore);
			MemCache.put(EVENTODDSMAP_ + event.id, odds);
		}
		List<OptionWithOdds> optionsWithOdds = new ArrayList<OptionWithOdds>();
		Set<Option> opts = odds.keySet();
		for (Iterator<Option> iterator = opts.iterator(); iterator.hasNext();) {
			Option option = iterator.next();
			optionsWithOdds.add(new OptionWithOdds(option, odds.get(option)));
		}
		return optionsWithOdds;
	}

	@SuppressWarnings("unchecked")
	protected List<EventDescriptor> refreshEventDescriptors(HttpServletRequest request, DataStore datastore,
			Engine engine, int from, int count) {
		List<EventDescriptor> eventDescriptors = loadEvents(request, datastore, engine);
		if (from == 0 && count == 0) {
			return eventDescriptors;
		} else {
			return slice(from, count, eventDescriptors);
		}
	}

	private List<EventDescriptor> loadEvents(HttpServletRequest request, DataStore datastore, Engine engine) {
		List<EventDescriptor> eventDescriptors = (List<EventDescriptor>) MemCache.get(ALL_EVENT_DESCRIPTORS);
		if (eventDescriptors == null) {
			eventDescriptors = getEventDescriptorsFromDB(datastore, engine);
			MemCache.put(ALL_EVENT_DESCRIPTORS, eventDescriptors);
		} else {
			logger.debug("Cache hit");
		}
		// Now filter for user
		List<EventDescriptor> userFilteredEvents = filterForUser(request, eventDescriptors);
		return userFilteredEvents;
	}

	private List<EventDescriptor> filterForUser(HttpServletRequest request, List<EventDescriptor> eventDescriptors) {
		EdvdSessionUser sessionUser = sessionUser(request);
		List<EventDescriptor> userFilteredEvents = new ArrayList<EventDescriptor>();
		for (EventDescriptor ed : eventDescriptors) {
			if (!ed.isPrivateEvent()) {
				logger.debug(String.format("Adding public event to userlist: %s",ed.getEvent().getName()));
				userFilteredEvents.add(ed);
			} else if(authenticatedSession(sessionUser)){								
				List<String> invitedList = ed.getEvent().getInvitedList();
				logger.debug(String.format("Looking at private event : %s, invites: %d",ed.getEvent().getName(),invitedList.size()));
				for (String email : invitedList) {
					String sue = sessionUser.getPlayer().getEmail();
					if (email.equals(sue)) {
						logger.debug(String.format("Adding private event to userlist: User %s has access to private event %s",
								sessionUser.getPlayer().email, ed.name));
						userFilteredEvents.add(ed);
					}
				}
			}
		}
		return userFilteredEvents;
	}

	private boolean authenticatedSession(EdvdSessionUser sessionUser) {
		return sessionUser!=null && sessionUser.authenticated;
	}

	private List<EventDescriptor> slice(int from, int count, List<EventDescriptor> eventDescriptors) {
		List<EventDescriptor> ret = null;
		int eventCount = eventDescriptors.size();
		if (eventDescriptors == null || eventCount == 0) {
			return eventDescriptors;
		}
		if (from > eventCount) {
			ret = new ArrayList<EventDescriptor>();
		} else {
			int to = from + count;
			if (to >= eventCount) {
				to = eventCount - 1;
			}
			logger.info("slice returns from=" + from + ", to=" + to);
			ret = eventDescriptors.subList(from, to);
		}
		logger.info("slice returns length: " + ret.size());
		return ret;
	}

	protected List<EventDescriptor> refreshEventDescriptors(DataStore datastore, Engine engine,
			HttpServletRequest request) {
		return refreshEventDescriptors(request, datastore, engine, 0, 0);
	}

	List<EventDescriptor> getEventDescriptorsFromDB(DataStore datastore, Engine engine) {
		ArrayList<EventDescriptor> eventDescriptors = new ArrayList<EventDescriptor>();
		List<Event> allEvents = datastore.listEvents(false);
		for (Iterator<Event> iterator = allEvents.iterator(); iterator.hasNext();) {
			Event anevent = (Event) iterator.next();
			String eventDesc = anevent.getDescription();
			EventDescriptor evdesc = new EventDescriptor(anevent, eventDesc, getOptionsWithOddsForEvent(anevent,
					datastore, engine));
			eventDescriptors.add(evdesc);
		}
		return eventDescriptors;
	}

	protected void logActivity(DataStore datastore, ActivityType type, String text, Player player) {
		datastore.logActivity(type, text, player);
	}

	@ExceptionHandler({ NotEnoughCreditException.class })
	public void notEnoughCreditException(HttpServletResponse response, NotEnoughCreditException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, "User has debt. Pay debt on userpage.");
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ NoSessionUserException.class })
	public void noSessionUserException(HttpServletResponse response, NoSessionUserException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, "No user on session");
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ IncompleteNewEventException.class })
	public void incompleteNewEventException(HttpServletResponse response, IncompleteNewEventException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ NegativeAmountException.class })
	public void negativeAmountException(HttpServletResponse response, NegativeAmountException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, "Cannot bet negative amount..");
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ NoSuchBettableEventException.class })
	public void noSuchBettableEventException(HttpServletResponse response, NegativeAmountException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, "No such event available for betting");
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ NotOwnerOfEventException.class })
	public void notOwnerOfEventException(HttpServletResponse response, NegativeAmountException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ WrongPasswordException.class })
	public void wrongPasswordException(HttpServletResponse response, WrongPasswordException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ NoValidAccountException.class })
	public void noValidAccountException(HttpServletResponse response, NoValidAccountException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ ToohighAmountException.class })
	public void toohighAmountException(HttpServletResponse response, ToohighAmountException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler({ EddeVeddeRuntimeException.class })
	public void eddeVeddeRuntimeException(HttpServletResponse response, EddeVeddeRuntimeException exception)
			throws IOException {
		logger.error(exception);
		response.addHeader(ERRORMESSAGE, exception.getMessage());
		response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	protected boolean sessionDirty(HttpServletRequest request) {
		Object dirty = request.getSession().getAttribute(SESSIONDIRTY);
		return dirty != null && dirty.equals("1");
	}

	protected void markSessionDirty(HttpServletRequest request) {
		request.getSession().setAttribute(SESSIONDIRTY, "1");
	}

}
